/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2011, Salzburg Research. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 * sschaffe
 *
 * File Info:
 * User: sschaffe
 * Date: 2011/01/07
 * Time: 12:22
 *
 * Project: KiWi2, kiwi-core module
 */

package kiwi.core.services.content;

import kiwi.core.api.config.ConfigurationService;
import kiwi.core.api.content.ContentItemService;
import kiwi.core.api.facading.FacadingService;
import kiwi.core.api.triplestore.ResourceService;
import kiwi.core.api.triplestore.TripleStore;
import kiwi.core.events.ContentItemEvent;
import kiwi.core.model.Constants;
import kiwi.core.model.content.ContentItem;
import kiwi.core.model.content.MediaContent;
import kiwi.core.model.content.TextContent;
import kiwi.core.model.rdf.KiWiResource;
import kiwi.core.model.rdf.KiWiTriple;
import kiwi.core.model.rdf.KiWiUriResource;
import kiwi.core.model.user.KiWiUser;
import kiwi.core.qualifiers.event.Created;
import kiwi.core.qualifiers.event.Removed;
import kiwi.core.qualifiers.event.Updated;
import kiwi.core.qualifiers.user.CurrentUser;
import nu.xom.Document;
import org.slf4j.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.*;

/**
 * The ContentItemService provides common methods for accessing, querying, creating, and storing
 * ContentItems in KiWi.
 *
 * @author Sebastian Schaffert
 *
 */
@Named("kiwi.core.contentItemService")
@ApplicationScoped
public class ContentItemServiceImpl implements ContentItemService {

    @Inject
    private Logger log;

    @Inject
    private ConfigurationService configurationService;

    @Inject
    private FacadingService facadingService;

    @Inject
    private ResourceService resourceService;

    @Inject
    private TripleStore tripleStore;


    @Inject @CurrentUser
    private KiWiUser currentUser;


    @Inject @Created
    private Event<ContentItemEvent> itemCreatedEvent;

    @Inject @Updated
    private Event<ContentItemEvent> itemUpdatedEvent;

    @Inject @Removed
    private Event<ContentItemEvent> itemRemovedEvent;

    /**
     * Create a new content item with the current system's base URI and a random unique postfix.
     * This method does not call persist - the created content item must be persisted manually.
     *
     * @return a newly initialised content item that can be used for further operations
     */
    public ContentItem createContentItem() {
        return createContentItem("core/content/"+ UUID.randomUUID().toString());
    }


    /**
     * Create a new content item with the current system's base URI and the uriPostfix appended. The actual
     * creation of the content item is currently performed by tripleStore.createUriResource ...
     *
     * @param uriPostfix the postfix of the URI to use; should not start with /
     * @return a newly initialised content item that can be used for further operations
     */
    public ContentItem createContentItem(String uriPostfix) {
        ContentItem result = getContentItemByUri(configurationService.getBaseUri()+uriPostfix);
        if(result == null) {
            KiWiUriResource resource = resourceService.createUriResource(configurationService.getBaseUri()+uriPostfix);

            result = facadingService.createFacade(resource,ContentItem.class);
            result.setAuthor(currentUser.getResource());
            result.setCreated(resource.getCreated());
            result.setTitle(uriPostfix);


            itemCreatedEvent.fire(new ContentItemEvent(result));
        }
        return result;
    }


    /**
     * Create or return a content item around the KiWi resource passed as argument. No text or media content
     * is created.
     * <p/>
     * If the resource passed as argument identifies an already existing content item, this content item is returned.
     * If the URI does not yet identify a content item, a new content item will be created using the facading
     * mechanism provided by facadingService.
     *
     * @param r
     * @return
     */
    @Override
    public ContentItem createContentItem(KiWiResource r) {
        return facadingService.createFacade(r,ContentItem.class);
    }

    /*
      * (non-Javadoc)
      *
      * @see kiwi.backend.KnowledgeSpaceI#listContentItems()
      */
	@SuppressWarnings("unchecked")
	public Set<ContentItem> getContentItems() {
        KiWiResource t_contentitem = resourceService.createUriResource(Constants.NS_KIWI_CORE+"ContentItem");

		Set<ContentItem> result = new HashSet<ContentItem>();
		result.addAll (facadingService.createFacade(resourceService.listResources(t_contentitem),ContentItem.class));
		return result;
	}




	/**
	 * returns true if user exists and false if not
	 *
	 * @param title the title of the content item that is tested
	 * @return true if content item with given title exists
	 */
	public boolean contentItemExists(String title) {
        return getContentItemByTitle(title) != null;
	}


    /**
     * Return a content item by its KiWi identifier. The KiWi identifier is either
     * <ul>
     * <li>for URI resources: uri::URI, where URI is an arbitrary URI following the
     * W3C standard</li>
     * <li>for anonymous resources: blank::ANONID, where ANONID is the internal
     * anonymous id of the blank node</li>
     * </ul>
     *
     * @param kiwiId
     * @return the content item with the given kiwi id, or null if it does not exist
     */
    @Override
    public ContentItem getContentItemByKiwiId(String kiwiId) {
        log.debug("requested KiWi content item with KiWi ID #0", kiwiId);
        // TODO Is it healthy to return null instead of throwing an exception?
        if (kiwiId == null) {
            return null;
        }
        String[] components = kiwiId.split("::");
        if (components[0].equals("uri")) {
            return getContentItemByUri(components[1]);
        } else if (components[0].equals("bnode")) {
            return getContentItemByAnonId(components[1]);
        } else {
            return null;
        }
    }

    /**
     * Return a content item by the title of the resource that is associated
     * with it. Obviously only works for content items with a URI resource, not
     * with anonymous resources.
     *
     * @param title the title to search for
     * @return the content item with the given title; if there is more than one result,
     *         returns an arbitrary content item of the result set
     */
    @Override
    public ContentItem getContentItemByTitle(String title) {
        return getContentItemByProperty(Constants.NS_KIWI_CORE+"title",title);
    }

    /**
     * Return a content item by the URI of the resource that is associated with
     * it. Obviously only works for content items with a URI resource, not with
     * anonymous resources.
     *
     * @param uri the uri of the resource associated with the content item
     * @return the content item with the given URI, or null if it does not exist
     */
    @Override
    public ContentItem getContentItemByUri(String uri) {
        return facadingService.createFacade(resourceService.getUriResource(uri),ContentItem.class);
    }

    /**
     * Return a content item by the AnonID of the resource that is associated
     * with it. Obviously only works for content items with a anonymous
     * resource, not with URI resources.
     *
     * @param anonId the internal id of this anonymous resource
     * @return the content item with the given anonymous id
     */
    @Override
    public ContentItem getContentItemByAnonId(String anonId) {
        return facadingService.createFacade(resourceService.getAnonResource(anonId),ContentItem.class);
    }


    private ContentItem getContentItemByProperty(String property, String value) {
        Collection<KiWiResource> items = resourceService.listResourcesByProperty(property,value);

        if(items.size() == 1) {
            return facadingService.createFacade(items.iterator().next(), ContentItem.class);
        } else if(items.size() > 1) {
            log.warn("no unique content item found for property {} and value {}; returning first result",property, value);
            return facadingService.createFacade(items.iterator().next(), ContentItem.class);
        } else {
            return null;
        }


    }


    /**
     * Create a new multimedia content item, using the data passed as argument.
     *
     * @param data     the data as byte array
     * @param mimeType the mime type in string format
     */
    @Override
    public ContentItem createMediaContentItem(byte[] data, String mimeType) {
        ContentItem contentItem = createContentItem();

 		updateMediaContentItem(contentItem,data,mimeType);

        return contentItem;
    }

    /**
     * Update the multimedia content of the item passed as argument, using the data, mime type and
     * filename provided. The method persists the updated content item and media content and
     * creates a new revision for the content item.
     *
     * @param item     the content item containing the media data
     * @param data     the data as byte array
     * @param mimeType the mime type in string format
     */
    @Override
    public void updateMediaContentItem(ContentItem item, byte[] data, String mimeType) {
        MediaContent mediaContent = new MediaContent(data,mimeType);
        item.setMediaContent(mediaContent);


        itemUpdatedEvent.fire(new ContentItemEvent(item));
    }

    /**
     * Create a new content item with the passed string as text content.
     * <p/>
     * This method automatically takes care of parsing the content into the internal KiWi format.
     * The content item and content are automatically stored in the database, and the new content
     * item is returned.
     *
     * @param content
     */
    @Override
    public ContentItem createTextContentItem(String content) {
        ContentItem contentItem = createContentItem();

        updateTextContentItem(contentItem,content);

        return contentItem;
    }

    /**
     * Update the text content of item using the html string content.
     * <p/>
     * This method automatically takes care of parsing the content into the internal KiWi format
     * and creates a new revision of the text content in case the content has changed.
     *
     * @param item    the item to update
     * @param content a string containing the HTML content to use as new text content
     */
    @Override
    public void updateTextContentItem(ContentItem item, String content) {
        // TODO: processing of content needs to take place before saving!

        TextContent tc = new TextContent(content,item.getLanguage());
        item.setTextContent(tc);

        itemUpdatedEvent.fire(new ContentItemEvent(item));
    }

    /**
     * Update the text content of item using the internal KiWi format
     * <p/>
     * and creates a new revision of the text content in case the content has changes.
     *
     * @param item
     * @param content
     */
    @Override
    public void updateTextContentItem(ContentItem item, Document content) {
        updateTextContentItem(item,TextContent.xom2string(content,true));
    }


    @Override
    public void updateTitle(ContentItem item, String title) {
        item.setTitle(title);

        itemUpdatedEvent.fire(new ContentItemEvent(item));
    }

    /**
     * Remove a content item. This method mainly marks the content item as deleted and then
     * saves it through the kiwiEntityManager.
     */
    @Override
    public void removeContentItem(ContentItem item) {
        // TODO: remove all triples having the item as subject or object and remove the resource facaded by the item
        for(KiWiTriple t : resourceService.listOutgoing(item.getDelegate())) {
            tripleStore.removeTriple(t);
        }
        for(KiWiTriple t : resourceService.listIncoming(item.getDelegate())) {
            tripleStore.removeTriple(t);
        }
        resourceService.removeResource(item.getDelegate());
    }

    /**
     * Lists all content items authored by an user.
     *
     * @param user
     * @return
     */
    @Override
    public List<ContentItem> getContentItemByAuthor(KiWiUser user) {
        Collection<KiWiResource> resources = resourceService.listResourcesByProperty(Constants.NS_KIWI_CORE+"hasAuthor",user.getResource());

        return new LinkedList<ContentItem>(facadingService.createFacade(resources,ContentItem.class));
    }

    /**
     * Lists all content items tagged by an user.
     *
     * @param user
     * @return
     */
    @Override
    public List<ContentItem> getContentItemTaggedByUser(KiWiUser user) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public List<ContentItem> getContentItemsByDate(Date since, Date until) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }
}
